[name].vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. <!-- @format -->
  2. <script lang="ts" setup>
  3. import { ArrowRight } from '@element-plus/icons-vue'
  4. import { Swiper, SwiperSlide } from 'swiper/vue'
  5. import { Navigation, Pagination } from 'swiper/modules'
  6. import dayjs from 'dayjs'
  7. import { ConstKeys } from '~/enums/const-enums'
  8. import { PageSizeEnum } from '~/enums/sizeEnum'
  9. import {
  10. getFeatureAlumDetailApi,
  11. getFeatureBrandsListApi,
  12. getFeatureDetailApi,
  13. getFeatureGoodsListApi,
  14. } from '~/api/model/feature'
  15. import img02 from '~/assets/images/featured_img02.png'
  16. import img03 from '~/assets/images/featured_img03.png'
  17. import img04 from '~/assets/images/featured_img04.png'
  18. import img05 from '~/assets/images/featured_img05.png'
  19. import 'swiper/css'
  20. import 'swiper/css/navigation'
  21. // import "swiper/css/pagination"
  22. import { useUserStore } from '@/stores/modules/user'
  23. enum Type {
  24. Detail = '0',
  25. Album = '1',
  26. }
  27. const userStore = useUserStore()
  28. const { isLogin } = storeToRefs(userStore)
  29. const { openLoginModal } = useLoginModal()
  30. const modules = [Navigation, Pagination]
  31. const brandList = ref<any>([])
  32. const swiperVertical = ref<any>(null)
  33. const detail = ref()
  34. const list = ref<any>([])
  35. const currentPage = ref(0)
  36. const total = ref(0)
  37. const page_size = ref(20)
  38. const isEffective = ref(true)
  39. const staticList = ref([
  40. {
  41. icon: img02,
  42. title: 'Source Manufacturers',
  43. subTitle:
  44. 'Selected product suppliers with the capability to develop unique products.',
  45. },
  46. {
  47. icon: img03,
  48. title: 'Competitive Pricing',
  49. subTitle:
  50. 'Get the best quality products at the most competitive wholesale prices.',
  51. },
  52. {
  53. icon: img04,
  54. title: 'Strict Quality Control',
  55. subTitle:
  56. 'Strict quality inspection to ensure the products meet our high standards.',
  57. },
  58. {
  59. icon: img05,
  60. title: 'Dedicated Support',
  61. subTitle:
  62. 'Smooth wholesale experience with dedicated customer manager service.',
  63. },
  64. ])
  65. const route = useRoute()
  66. const paintingId = route.query.paintingId
  67. const slug = route.params.name
  68. const { data, pending, error, refresh } = await useAsyncData(
  69. 'featured-detail',
  70. () => $fetch(`${ConstKeys.DOMAINPRO}/client/topic/detail`, { params: { slug } }),
  71. )
  72. const seoData = data.value?.result
  73. useHead({
  74. title: seoData?.title,
  75. meta: [
  76. {
  77. name: 'description',
  78. content: seoData?.headImageText,
  79. },
  80. {
  81. property: 'og:title',
  82. content: seoData?.title,
  83. },
  84. {
  85. property: 'og:description',
  86. content: seoData?.headImageText,
  87. },
  88. {
  89. property: 'og:type',
  90. content: 'website',
  91. },
  92. {
  93. property: 'twitter:title',
  94. content: seoData?.title,
  95. },
  96. {
  97. property: 'twitter:description',
  98. content: seoData?.headImageText,
  99. },
  100. {
  101. property: 'twitter:card',
  102. content: 'summary_large_image',
  103. },
  104. ],
  105. link: [
  106. {
  107. rel: 'canonical',
  108. href: `${ConstKeys.DOMAINPRO}/collections/${slug}`,
  109. },
  110. ],
  111. })
  112. const isType = computed(() => detail.value?.type === Type.Detail)
  113. async function getFeatureDetail() {
  114. try {
  115. const result = await getFeatureDetailApi({ slug })
  116. detail.value = result
  117. }
  118. catch (error) {
  119. console.log('error', error)
  120. }
  121. }
  122. async function getFeatureAlumDetail() {
  123. try {
  124. const result = await getFeatureAlumDetailApi({ paintingId })
  125. isEffective.value = true
  126. detail.value = result
  127. }
  128. catch (error: any) {
  129. if (error.code === 10010)
  130. isEffective.value = false
  131. console.log('error', error)
  132. }
  133. }
  134. async function getFeatureProductList(
  135. pageNo = PageSizeEnum.PAGE,
  136. pageSize = page_size.value,
  137. ) {
  138. const res: any = await getFeatureGoodsListApi({
  139. pageNo,
  140. pageSize,
  141. topicSlug: slug,
  142. })
  143. list.value = res.records
  144. currentPage.value = res.current
  145. total.value = res.total
  146. }
  147. function updatePage(currentPage: number, pageSize: number) {
  148. getFeatureProductList(currentPage, pageSize)
  149. }
  150. async function getHomeBrandList() {
  151. try {
  152. const data: any = await getFeatureBrandsListApi({
  153. pageNo: 1,
  154. pageSize: page_size.value,
  155. topicSlug: slug,
  156. })
  157. brandList.value = data.records
  158. }
  159. catch (error) {
  160. console.log('error', error)
  161. }
  162. }
  163. function onVerticalSwiper(swiper: any) {
  164. swiperVertical.value = swiper
  165. }
  166. function onClickLeft() {
  167. swiperVertical.value.slidePrev()
  168. }
  169. function onClickRight() {
  170. swiperVertical.value.slideNext()
  171. }
  172. function getData() {
  173. if (!paintingId) {
  174. getHomeBrandList()
  175. getFeatureProductList()
  176. getFeatureDetail()
  177. }
  178. else {
  179. getFeatureAlumDetail()
  180. }
  181. }
  182. getData()
  183. async function openLogin() {
  184. await openLoginModal()
  185. }
  186. </script>
  187. <template>
  188. <div class="pos-relative">
  189. <div v-if="isType" class="fixed-image">
  190. <img
  191. :src="detail?.headImageUrl"
  192. class="w-full h-360px object-cover"
  193. alt=""
  194. srcset=""
  195. >
  196. </div>
  197. <div class="scrollable-content">
  198. <div
  199. v-if="isType"
  200. :class="isType ? 'h-360px' : ''"
  201. class="content-header"
  202. >
  203. <h1 class="!text-36px !fw-700 custom-title-font">
  204. {{ detail?.title }}
  205. </h1>
  206. <div class="text-18px">
  207. {{ detail?.headImageText }}
  208. </div>
  209. </div>
  210. <div :class="isType ? 'bg-#fff' : ''">
  211. <div v-if="isEffective" class="w-1400px mx-auto pt-40px pb-60px">
  212. <el-breadcrumb :separator-icon="ArrowRight">
  213. <el-breadcrumb-item :to="{ path: '/' }">
  214. Home
  215. </el-breadcrumb-item>
  216. <el-breadcrumb-item :to="{ path: '/collections' }">
  217. Collections
  218. </el-breadcrumb-item>
  219. <el-breadcrumb-item> {{ detail?.title }}</el-breadcrumb-item>
  220. </el-breadcrumb>
  221. </div>
  222. <div v-if="isEffective" class="w-1400px mx-auto mb-60px">
  223. <h1 class="!text-26px !fw-700 text-#363C40 custom-title-font">
  224. {{ detail?.title }}
  225. </h1>
  226. <div class="flex my-24px text-#7C7C7C text-18px">
  227. <div class="mr-48px">
  228. {{ dayjs(detail?.createTime).format('MMMM D, YYYY') }}
  229. </div>
  230. <div v-if="isType">
  231. {{ detail?.merchandiseQuantity }} products
  232. </div>
  233. </div>
  234. <div
  235. class="text-18px lh-24px line-clamp-3 text-#666"
  236. :title="detail?.headImageText"
  237. >
  238. {{ detail?.headImageText }}
  239. </div>
  240. </div>
  241. <div>
  242. <div v-if="isType">
  243. <div
  244. v-if="isLogin || !detail?.userEnable"
  245. class="w-1400px mx-auto pb-160px"
  246. >
  247. <div
  248. v-if="list.length"
  249. class="grid grid-gap-x-65px grid-gap-y-80px grid-cols-4"
  250. >
  251. <div v-for="item in list" :key="item.id">
  252. <common-goods-item :item />
  253. </div>
  254. </div>
  255. <common-empty v-else class="py-50px" title="">
  256. <template #icon>
  257. <img
  258. src="~/assets/images/featured_empty.png"
  259. class="w-200px h-200px"
  260. alt=""
  261. srcset=""
  262. >
  263. </template>
  264. </common-empty>
  265. <div v-if="list.length" class="mt-60px flex justify-center">
  266. <el-pagination
  267. v-model:current-page="currentPage"
  268. :page-size="page_size"
  269. :pager-count="10"
  270. layout="prev, pager, next"
  271. :total="total"
  272. @change="updatePage"
  273. />
  274. </div>
  275. </div>
  276. <div
  277. v-else
  278. class="w-1400px mx-auto pt-80px pb-160px flex justify-center"
  279. >
  280. <div class="cursor-pointer" @click="openLogin">
  281. <svgo-lock class="!w-100px !h-100px text-#999 m-auto" />
  282. <div class="text-#999 text-42px mt-20px">
  283. Login to view
  284. </div>
  285. </div>
  286. </div>
  287. </div>
  288. <div v-else class="text-#000 w-1400px mx-auto pb-160px">
  289. <div
  290. v-if="isEffective"
  291. class="text-#333333 content-detail custom-html"
  292. v-html="detail?.content"
  293. />
  294. <div v-else class="pt-160px">
  295. Sorry, the shared content has expired.
  296. </div>
  297. </div>
  298. <div
  299. v-if="isType"
  300. class="flex justify-center items-center py-82px bg-#FAF5F1"
  301. >
  302. <div class="w-600px">
  303. <h2 class="fw-700 text-32px text-#333 !mb-60px">
  304. Quality Wholesale, Inspiring Global Retail
  305. </h2>
  306. <div
  307. v-for="(item, index) in staticList"
  308. :key="index"
  309. class="flex items-center mb-40px last:mb-0"
  310. >
  311. <img :src="item.icon" alt="" srcset="" class="w-40px h-40px">
  312. <div class="ml-30px w-430px">
  313. <h3 class="!fw-500 text-26px text-#333 lh-30px">
  314. {{ item.title }}
  315. </h3>
  316. <div class="mt-10px text-18px text-#999 lh-28px">
  317. {{ item.subTitle }}
  318. </div>
  319. </div>
  320. </div>
  321. </div>
  322. <img
  323. src="~/assets/images/featured_img01.png"
  324. class="w-566px h-480px object-cover ml-10px"
  325. alt=""
  326. srcset=""
  327. >
  328. </div>
  329. <div v-if="isType" class="py-160px w-1400px mx-auto">
  330. <h2 class="!mb-40px fw-700 text-40px text-#363C40">
  331. Collection Brands
  332. </h2>
  333. <div v-if="brandList.length">
  334. <div class="w-1300px mx-auto pos-relative">
  335. <div
  336. class="pos-absolute cursor-pointer left--46px top-200px w-28px h-28px transform-translate-y--50% cursor-not-allowed !cursor-pointer flex justify-center items-center"
  337. @click="onClickLeft()"
  338. >
  339. <img
  340. src="~/assets/images/arrow_left.png"
  341. alt=""
  342. class="w-26px h-26px"
  343. srcset=""
  344. >
  345. </div>
  346. <div
  347. class="pos-absolute cursor-pointer right--46px top-200px w-28px h-28px transform-translate-y--50% cursor-not-allowed !cursor-pointer flex justify-center items-center"
  348. @click="onClickRight()"
  349. >
  350. <img
  351. src="~/assets/images/arrow_right.png"
  352. alt=""
  353. class="w-26px h-26px"
  354. srcset=""
  355. >
  356. </div>
  357. <Swiper
  358. :slides-per-view="4"
  359. :space-between="55"
  360. :modules="modules"
  361. :loop="true"
  362. :navigation="false"
  363. :pagination="true"
  364. class="pos-relative"
  365. @swiper="onVerticalSwiper"
  366. >
  367. <SwiperSlide v-for="(item, index) in brandList" :key="index">
  368. <common-brand-item :item="item" />
  369. </SwiperSlide>
  370. </Swiper>
  371. </div>
  372. </div>
  373. <common-empty v-else class="pt-50px" title="">
  374. <template #icon>
  375. <img
  376. src="~/assets/images/featured_empty.png"
  377. class="w-200px h-200px"
  378. alt=""
  379. srcset=""
  380. >
  381. </template>
  382. </common-empty>
  383. </div>
  384. </div>
  385. </div>
  386. <AppFooter class="bg-#fff" />
  387. </div>
  388. </div>
  389. </template>
  390. <style lang="less" scoped>
  391. .fixed-image {
  392. position: fixed;
  393. top: 151px;
  394. left: 0;
  395. width: 100%;
  396. height: 360px;
  397. z-index: 1;
  398. overflow: hidden;
  399. }
  400. .scrollable-content {
  401. position: relative;
  402. z-index: 2;
  403. width: 100%;
  404. color: white;
  405. .content-header {
  406. display: flex;
  407. // height: 360px;
  408. flex-direction: column;
  409. justify-content: center;
  410. align-items: center;
  411. text-align: center;
  412. background: rgba(0, 0, 0, 0.4);
  413. /* 半透明背景使文字在图片上更可读 */
  414. }
  415. ::v-deep(.content-detail) {
  416. font-family: sans-serif;
  417. h2 {
  418. font-size: 1.5em;
  419. font-family: 'CustomTitleFont';
  420. margin-top: 1em !important;
  421. margin-bottom: 1em !important;
  422. font-weight: bold;
  423. }
  424. h3 {
  425. display: block;
  426. font-size: 1.17em;
  427. margin-block-start: 1em;
  428. margin-block-end: 1em;
  429. margin-inline-start: 0px;
  430. margin-inline-end: 0px;
  431. margin-bottom: 1em !important;
  432. font-weight: bold;
  433. unicode-bidi: isolate;
  434. font-family: 'CustomTitleFont';
  435. }
  436. p {
  437. display: block;
  438. margin-block-start: 1em;
  439. margin-block-end: 1em;
  440. margin-inline-start: 0px;
  441. margin-inline-end: 0px;
  442. unicode-bidi: isolate;
  443. }
  444. ul {
  445. display: block;
  446. list-style-type: disc;
  447. margin-block-start: 1em;
  448. margin-block-end: 1em;
  449. margin-inline-start: 0px;
  450. margin-inline-end: 0px;
  451. padding-inline-start: 40px;
  452. unicode-bidi: isolate;
  453. li {
  454. display: list-item;
  455. text-align: -webkit-match-parent;
  456. unicode-bidi: isolate;
  457. }
  458. }
  459. ol {
  460. list-style-type: decimal;
  461. display: block;
  462. list-style-type: decimal;
  463. margin-block-start: 1em;
  464. margin-block-end: 1em;
  465. margin-inline-start: 0px;
  466. margin-inline-end: 0px;
  467. padding-inline-start: 40px;
  468. unicode-bidi: isolate;
  469. }
  470. }
  471. }
  472. </style>